package com.itdl.lock.anno;

import com.itdl.lock.config.RedisLockConfig;
import com.itdl.lock.enums.LockErrCode;
import com.itdl.lock.enums.LockType;
import com.itdl.lock.exception.RedisLockException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * Redis分布式锁的切面逻辑实现
 */
@ConditionalOnProperty(value = "redis.enable", havingValue = "true")    // 开启redis.enable=true时生效
@AutoConfigureBefore(RedisLockConfig.class)
@Aspect
@Configuration
@Slf4j
public class RedisLockAop {

    @Resource
    private RedissonClient redissonClient;

    /**
     * 切点
     */
    @Pointcut("@annotation(com.itdl.lock.anno.RedisLock)")
    public void pointcut(){

    }


    /**
     * 环绕通知 注解针对的是方法，这里切点也获取方法进行处理就可以了
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 获取方法上的分布式锁注解
        RedisLock redisLock = method.getDeclaredAnnotation(RedisLock.class);

        // 获取注解的参数
        // 锁名称
        String lockName = redisLock.lockName();
        // 锁类型
        LockType lockType = redisLock.lockType();

        // 获取RedissonClient的Lock
        RLock lock = getRLock(lockName, lockType, redisLock);

        //获取到锁后, 开始执行方法，执行完毕后释放锁
        try {
            log.debug("=========>>>获取锁成功, 即将执行业务逻辑：{}", lockName);
            Object proceed = joinPoint.proceed();
            // 释放锁
            if (redisLock.immediatelyUnLock()) {
                //是否立即释放锁
                lock.unlock();
            }
            log.debug("=========>>>获取锁成功且执行业务逻辑成功：{}", lockName);
            return proceed;
        } catch (Exception e) {
            log.error("=========>>>获取锁成功但执行业务逻辑失败：{}", lockName);
            e.printStackTrace();
            throw new RedisLockException(LockErrCode.EXEC_BUSINESS_ERR);
        }finally {
            // 查询当前线程是否保持此锁定 被锁定则解锁
            lock.unlock();
            log.debug("=========>>>释放锁成功：{}", lockName);
        }
    }

    /**
     * 根据锁名称和类型创建锁
     * @param lockName 锁名称
     * @param lockType 锁类型
     * @return 锁
     */
    private RLock getRLock(String lockName, LockType lockType, RedisLock redisLock) throws InterruptedException {
        RLock lock;
        switch (lockType){
            case FAIR_LOCK:
                lock = redissonClient.getFairLock(lockName);
                break;
            case READ_LOCK:
                lock = redissonClient.getReadWriteLock(lockName).readLock();
                break;
            case WRITE_LOCK:
                lock = redissonClient.getReadWriteLock(lockName).writeLock();
                break;
            default:
                // 默认加可重入锁，也就是普通的分布式锁
                lock = redissonClient.getLock(lockName);
                break;
        }
        // 首先尝试获取锁，如果在规定时间内没有获取到锁，则调用lock等待锁，直到获取锁为止
        if (lock.tryLock()) {
            lock.tryLock(redisLock.waitTime(), redisLock.leaseTime(), redisLock.timeUnit());
        }else {
            // 如果leaseTime>0，规定时间内获取锁，超时则自动释放锁
            long leaseTime = redisLock.leaseTime();
            if (leaseTime > 0) {
                lock.lock(redisLock.leaseTime(), redisLock.timeUnit());
            } else {
                // 自动释放锁时间设置为0或者负数，则加锁不设置超时时间
                lock.lock();
            }
        }
        return lock;
    }
}
